package com.willowtreeapps.opentest4k

import java.io.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue

class AssertionFailedErrorTests4j {

    @Test fun serializationWorksForAssertionFailedErrorWithMessageAndExpectedAndActualValues() {
        val error = serializeAndDeserialize(AssertionFailedError("a message", "foo", "bar"))

        assertEquals("a message", error.message)
        assertTrue(error.isExpectedDefined)
        assertEquals("foo", error.expected?.value)
        assertTrue(error.isActualDefined)
        assertEquals("bar", error.actual?.value)
    }

    @Test fun serializationWorksForAssertionFailedErrorWithoutAnyValues() {
        val error = serializeAndDeserialize(AssertionFailedError())

        assertEquals(EMPTY, error.message)
        assertFalse(error.isExpectedDefined)
        assertNull(error.expected)
        assertFalse(error.isActualDefined)
        assertNull(error.actual)
    }

    @Test fun deserializationOfAssertionFailedErrorWorksForVersion_1_0_0() {
        // Generated using:
        // FileOutputStream outputStream = new FileOutputStream("src/test/resources/AssertionFailedError_serializedVersion_1_0_0.out");
        // serialize(new AssertionFailedError("message", "foo", "bar"), outputStream);
        // outputStream.close();
        val error = deserializeClasspathResource(
            "/AssertionFailedError_serializedVersion_1_0_0.out"
        ) as AssertionFailedError
        assertEquals("message", error.message)
        assertTrue(error.isExpectedDefined)
        assertEquals("foo", error.expected!!.value)
        assertTrue(error.isActualDefined)
        assertEquals("bar", error.actual!!.value)
    }

    @Test fun ephemeralValueIsOmittedFromSerialization() {
        class NonSerializable {
            val guid = "8675309"
        }

        val error = serializeAndDeserialize(
            AssertionFailedError("a message", NonSerializable(), NonSerializable())
        )
        assertEquals("a message", error.message)
        assertTrue(error.isExpectedDefined)
        assertNull(error.expected!!.value)
        assertNull(error.expected!!.ephemeralValue)
        assertTrue(error.isActualDefined)
        assertNull(error.actual!!.value)
        assertNull(error.actual!!.ephemeralValue)
    }

    private fun serializeAndDeserialize(originalError: AssertionFailedError): AssertionFailedError {
        val bytes = serialize(originalError)
        val deserializedObject: AssertionFailedError = deserialize(bytes)
        assertEquals(AssertionFailedError::class, deserializedObject::class)
        return deserializedObject
    }


    private fun deserializeClasspathResource(name: String): Any {
        val serialized = getResourceAsSerialized(name)
        return deserialize(serialized)
    }

    private fun getResourceAsSerialized(name: String): ByteArray {
        val inputStream: InputStream = javaClass.getResourceAsStream(name)!!
        inputStream.use {
            return it.readBytes()
        }
    }

    private inline fun <reified T : Any> deserialize(bytes: ByteArray): T {
        val `in` = ObjectInputStream(ByteArrayInputStream(bytes))
        @Suppress("UNCHECKED_CAST")
        return `in`.readObject() as T
    }

    private fun serialize(`object`: Any): ByteArray {
        val byteArrayOutputStream = ByteArrayOutputStream()
        val out = ObjectOutputStream(byteArrayOutputStream)
        out.writeObject(`object`)
        out.flush()
        return byteArrayOutputStream.toByteArray()
    }
}